import 'dart:math' as math;
import 'dart:typed_data';

import 'package:vector_math/vector_math.dart';
import 'package:vector_math_test/src/test_utils.dart';

import '../common/test_page.dart';

class Vector3TestPage extends TestPage{
  Vector3TestPage(super.title) {
    group('Vector3', () {
      test('dot3(Vector3 x, Vector3 y) => x.dot(y);', testVector3DotProduct);
      test('Matrix3.copy(Matrix3 other), Vector3()..postmultiply(Matrix3 arg)', testVector3Postmultiplication);
      test('cross3(Vector3 x, Vector3 y, Vector3 out)', testVector3CrossProduct);
      test('Vector3().reflect(Vector3 normal)', testVector3Reflect);
      test('Vector3().applyProjection(Matrix4 arg)', testVector3Projection);
      test('Vector3().length, Vector2().length2', testVector3Length);
      test('Vector3().length(double value)', testVector3SetLength);
      test('Vector3().negate()', testVector3Negate);
      test('Vector3(), Vector3.all(double value), Vector3.random([math.Random? rng])', testVector3Constructor);
      test('Vector3().add(Vector3 arg), Vector3().addScaled(Vector3 arg, double factor)', testVector3Add);
      test('Vector3().min(Vector3 a, Vector3 b, Vector3 result), Vector3().max(Vector3 a, Vector3 b, Vector3 result)', testVector3MinMax);
      test('Vector3().mix(Vector3 min, Vector3 max, double a, Vector3 result)', testVector3Mix);
      test('Vector3().distanceTo(Vector3 arg)', testVector3DistanceTo);
      test('Vector3().distanceToSquared(Vector3 arg)', testVector3DistanceToSquared);
      test('Vector3().angleTo(Vector3 other)', testVector3AngleTo);
      test('Vector3().angleToSigned(Vector3 other, Vector3 normal)', testVector3AngleToSigned);
      test('Vector3.fromFloat32List(this._v3storage)', testVector3InstacinfFromFloat32List);
      test('Vector3.fromBuffer(ByteBuffer buffer, int offset)', testVector3InstacingFromByteBuffer);
      test('Vector3()..clamp(Vector3 min, Vector3 max)', testVector3Clamp);
      test('Vector3()..clampScalar(double min, double max)', testVector3ClampScalar);
      test('Vector3()..floor()', testVector3Floor);
      test('Vector3()..ceil()', testVector3Ceil);
      test('Vector3()..round()', testVector3Round);
      test('Vector3()..roundToZero()', testVector3RoundToZero);
      test('Vector3()..applyQuaternion(Quaternion arg)', testVector3ApplyQuaternion);
    });
  }

  void testVector3InstacinfFromFloat32List() {
    final float32List = Float32List.fromList([1.0, 2.0, 3.0]);
    final input = Vector3.fromFloat32List(float32List);

    expect(input.x, 1.0);
    expect(input.y, 2.0);
    expect(input.z, 3.0);
  }

  void testVector3InstacingFromByteBuffer() {
    final float32List = Float32List.fromList([1.0, 2.0, 3.0, 4.0]);
    final buffer = float32List.buffer;
    final zeroOffset = Vector3.fromBuffer(buffer, 0);
    final offsetVector = Vector3.fromBuffer(buffer, Float32List.bytesPerElement);

    expect(zeroOffset.x, 1.0);
    expect(zeroOffset.y, 2.0);
    expect(zeroOffset.z, 3.0);

    expect(offsetVector.x, 2.0);
    expect(offsetVector.y, 3.0);
    expect(offsetVector.z, 4.0);
  }

  void testVector3Add() {
    final a = Vector3(5.0, 7.0, 3.0);
    final b = Vector3(3.0, 8.0, 2.0);

    a.add(b);
    expect(a.x, 8.0);
    expect(a.y, 15.0);
    expect(a.z, 5.0);

    b.addScaled(a, 0.5);
    expect(b.x, 7.0);
    expect(b.y, 15.5);
    expect(b.z, 4.5);
  }

  void testVector3MinMax() {
    final a = Vector3(5.0, 7.0, -3.0);
    final b = Vector3(3.0, 8.0, 2.0);

    final result = Vector3.zero();

    Vector3.min(a, b, result);
    expect(result.x, 3.0);
    expect(result.y, 7.0);
    expect(result.z, -3.0);

    Vector3.max(a, b, result);
    expect(result.x, 5.0);
    expect(result.y, 8.0);
    expect(result.z, 2.0);
  }

  void testVector3Mix() {
    final a = Vector3(5.0, 7.0, 3.0);
    final b = Vector3(3.0, 8.0, 2.0);

    final result = Vector3.zero();

    Vector3.mix(a, b, 0.5, result);
    expect(result.x, 4.0);
    expect(result.y, 7.5);
    expect(result.z, 2.5);

    Vector3.mix(a, b, 0.0, result);
    expect(result.x, 5.0);
    expect(result.y, 7.0);
    expect(result.z, 3.0);

    Vector3.mix(a, b, 1.0, result);
    expect(result.x, 3.0);
    expect(result.y, 8.0);
    expect(result.z, 2.0);
  }

  void testVector3DotProduct() {
    final inputA = <Vector3>[];
    final inputB = <Vector3>[];
    final expectedOutput = <double>[];
    inputA.add(parseVector<Vector3>('''0.417267069084370
                                     0.049654430325742
                                     0.902716109915281'''));
    inputB.add(parseVector<Vector3>('''0.944787189721646
                                     0.490864092468080
                                     0.489252638400019'''));
    expectedOutput.add(0.860258396944727);
    assert(inputA.length == inputB.length);
    assert(inputB.length == expectedOutput.length);
    for (var i = 0; i < inputA.length; i++) {
      final output1 = dot3(inputA[i], inputB[i]);
      final output2 = dot3(inputB[i], inputA[i]);
      relativeTest(output1, expectedOutput[i]);
      relativeTest(output2, expectedOutput[i]);
    }
  }

  void testVector3Postmultiplication() {
    final inputMatrix =
        (Matrix3.rotationX(.4)) * (Matrix3.rotationZ(.5)) as Matrix3;
    final inputVector = Vector3(1.0, 2.0, 3.0);
    final inputInv = Matrix3.copy(inputMatrix);
    inputInv.invert();
    final resultOld = inputMatrix.transposed() * inputVector as Vector3;
    final resultOldvInv = inputInv * inputVector as Vector3;
    final resultNew = inputVector..postmultiply(inputMatrix);

    expect(resultNew.x, resultOld.x);
    expect(resultNew.y, resultOld.y);
    expect(resultNew.z, resultOld.z);
    expect(resultNew.x, resultOldvInv.x);
    expect(resultNew.y, resultOldvInv.y);
    expect(resultNew.z, resultOldvInv.z);
  }

  void testVector3CrossProduct() {
    final inputA = <Vector3>[];
    final inputB = <Vector3>[];
    final expectedOutput = <Vector3>[];

    inputA.add(parseVector<Vector3>('''0.417267069084370
                                     0.049654430325742
                                     0.902716109915281'''));
    inputB.add(parseVector<Vector3>('''0.944787189721646
                                     0.490864092468080
                                     0.489252638400019'''));
    expectedOutput.add(parseVector<Vector3>(''' -0.418817363004761
                                               0.648725602136344
                                               0.157908551498227'''));

    inputA.add(parseVector<Vector3>('''0.944787189721646
                                     0.490864092468080
                                     0.489252638400019'''));
    inputB.add(parseVector<Vector3>('''0.417267069084370
                                     0.049654430325742
                                     0.902716109915281'''));
    expectedOutput.add(parseVector<Vector3>(''' 0.418817363004761
                                             -0.648725602136344
                                             -0.157908551498227'''));

    assert(inputA.length == inputB.length);
    assert(inputB.length == expectedOutput.length);

    for (var i = 0; i < inputA.length; i++) {
      final output = Vector3.zero();
      cross3(inputA[i], inputB[i], output);
      relativeTest(output, expectedOutput[i]);
    }

    {
      final x = Vector3(1.0, 0.0, 0.0);
      final y = Vector3(0.0, 1.0, 0.0);
      final z = Vector3(0.0, 0.0, 1.0);
      Vector3 output;

      output = x.cross(y);
      relativeTest(output, Vector3(0.0, 0.0, 1.0));
      output = y.cross(x);
      relativeTest(output, Vector3(0.0, 0.0, -1.0));

      output = x.cross(z);
      relativeTest(output, Vector3(0.0, -1.0, 0.0));
      output = z.cross(x);
      relativeTest(output, Vector3(0.0, 1.0, 0.0));

      output = y.cross(z);
      relativeTest(output, Vector3(1.0, 0.0, 0.0));
      output = z.cross(y);
      relativeTest(output, Vector3(-1.0, 0.0, 0.0));
    }
  }

  void testVector3Constructor() {
    final v1 = Vector3(2.0, 4.0, -1.5);
    expect(v1.x, 2.0);
    expect(v1.y, 4.0);
    expect(v1.z, -1.5);

    final v2 = Vector3.all(2.0);
    expect(v2.x, 2.0);
    expect(v2.y, 2.0);
    expect(v2.z, 2.0);

    final v3 = Vector3.random(math.Random());
    expect(v3.x, '');
    expect(v3.y, '');
    expect(v3.z, '');
  }

  void testVector3Length() {
    final a = Vector3(5.0, 7.0, 3.0);

    relativeTest(a.length, 9.1104);
    relativeTest(a.length2, 83.0);

    relativeTest(a.normalize(), 9.1104);
    relativeTest(a.x, 0.5488);
    relativeTest(a.y, 0.7683);
    relativeTest(a.z, 0.3292);
  }

  void testVector3SetLength() {
    final v0 = Vector3(1.0, 2.0, 1.0);
    final v1 = Vector3(3.0, -2.0, 2.0);
    final v2 = Vector3(-1.0, 2.0, -2.0);
    final v3 = Vector3(1.0, 0.0, 0.0);

    v0.length = 0.0;
    relativeTest(v0, Vector3.zero());
    relativeTest(v0.length, 0.0);

    v1.length = 2.0;
    relativeTest(
        v1, Vector3(1.4552137851715088, -0.9701424837112427, 0.9701424837112427));
    relativeTest(v1.length, 2.0);

    v2.length = 0.5;
    relativeTest(v2,
        Vector3(-0.1666666716337204, 0.3333333432674408, -0.3333333432674408));
    relativeTest(v2.length, 0.5);

    v3.length = -1.0;
    relativeTest(v3, Vector3(-1.0, 0.0, 0.0));
    relativeTest(v3.length, 1.0);
  }

  void testVector3Negate() {
    final vec3 = Vector3(1.0, 2.0, 3.0);
    vec3.negate();
    expect(vec3.x, -1.0);
    expect(vec3.y, -2.0);
    expect(vec3.z, -3.0);
  }

  void testVector3Reflect() {
    var v = Vector3(5.0, 0.0, 0.0);
    v.reflect(Vector3(-1.0, 0.0, 0.0));
    expect(v.x, -5.0);
    expect(v.y, 0.0);
    expect(v.y, 0.0);

    v = Vector3(0.0, 5.0, 0.0);
    v.reflect(Vector3(0.0, -1.0, 0.0));
    expect(v.x, 0.0);
    expect(v.y, -5.0);
    expect(v.z, 0.0);

    v = Vector3(0.0, 0.0, 5.0);
    v.reflect(Vector3(0.0, 0.0, -1.0));
    expect(v.x, 0.0);
    expect(v.y, 0.0);
    expect(v.z, -5.0);

    v = Vector3(-5.0, 0.0, 0.0);
    v.reflect(Vector3(1.0, 0.0, 0.0));
    expect(v.x, 5.0);
    expect(v.y, 0.0);
    expect(v.y, 0.0);

    v = Vector3(0.0, -5.0, 0.0);
    v.reflect(Vector3(0.0, 1.0, 0.0));
    expect(v.x, 0.0);
    expect(v.y, 5.0);
    expect(v.z, 0.0);

    v = Vector3(0.0, 0.0, -5.0);
    v.reflect(Vector3(0.0, 0.0, 1.0));
    expect(v.x, 0.0);
    expect(v.y, 0.0);
    expect(v.z, 5.0);

    v = Vector3(4.0, 4.0, 4.0);
    v.reflect(Vector3(-1.0, -1.0, -1.0).normalized());
    relativeTest(v.x, -4.0);
    relativeTest(v.y, -4.0);
    relativeTest(v.z, -4.0);

    v = Vector3(-4.0, -4.0, -4.0);
    v.reflect(Vector3(1.0, 1.0, 1.0).normalized());
    relativeTest(v.x, 4.0);
    relativeTest(v.y, 4.0);
    relativeTest(v.z, 4.0);

    v = Vector3(10.0, 20.0, 2.0);
    v.reflect(Vector3(-10.0, -20.0, -2.0).normalized());
    relativeTest(v.x, -10.0);
    relativeTest(v.y, -20.0);
    relativeTest(v.z, -2.0);
  }

  void testVector3Projection() {
    final v = Vector3(1.0, 1.0, 1.0);
    final a = 2.0 / 3.0;
    final b = 1.0 / 3.0;
    final m =
    Matrix4(a, b, -b, 0.0, b, a, b, 0.0, -b, b, a, 0.0, 0.0, 0.0, 0.0, 1.0);

    v.applyProjection(m);
    relativeTest(v.x, a);
    relativeTest(v.y, 4.0 / 3.0);
    relativeTest(v.z, a);
  }

  void testVector3DistanceTo() {
    final a = Vector3(1.0, 1.0, 1.0);
    final b = Vector3(1.0, 3.0, 1.0);
    final c = Vector3(1.0, 1.0, -1.0);

    expect(a.distanceTo(b), 2.0);
    expect(a.distanceTo(c), 2.0);
  }

  void testVector3DistanceToSquared() {
    final a = Vector3(1.0, 1.0, 1.0);
    final b = Vector3(1.0, 3.0, 1.0);
    final c = Vector3(1.0, 1.0, -1.0);

    expect(a.distanceToSquared(b), 4.0);
    expect(a.distanceToSquared(c), 4.0);
  }

  void testVector3AngleTo() {
    final v0 = Vector3(1.0, 0.0, 0.0);
    final v1 = Vector3(0.0, 1.0, 0.0);
    final v2 = Vector3(1.0, 1.0, 0.0);
    final v3 = v2.normalized();
    final tol = 1e-8;

    expect(v0.angleTo(v0), 0.0);
    expect(v0.angleTo(v1), math.pi / 2.0);
    expect(v0.angleTo(v2), '');
    expect(v0.angleTo(v3), '');
  }

  void testVector3AngleToSigned() {
    final v0 = Vector3(1.0, 0.0, 0.0);
    final v1 = Vector3(0.0, 1.0, 0.0);
    final n = Vector3(0.0, 0.0, 1.0);

    expect(v0.angleToSigned(v0, n), 0.0);
    expect(v0.angleToSigned(v1, n), math.pi / 2.0);
    expect(v1.angleToSigned(v0, n), -math.pi / 2.0);
  }

  void testVector3Clamp() {
    final x = 2.0, y = 3.0, z = 4.0;
    final v0 = Vector3(x, y, z);
    final v1 = Vector3(-x, -y, -z);
    final v2 = Vector3(-2.0 * x, 2.0 * y, -2.0 * z)..clamp(v1, v0);

    expect(v2.storage, '');
  }

  void testVector3ClampScalar() {
    final x = 2.0;
    final v0 = Vector3(-2.0 * x, 2.0 * x, -2.0 * x)..clampScalar(-x, x);

    expect(v0.storage, '');
  }

  void testVector3Floor() {
    final v0 = Vector3(-0.1, 0.1, -0.1)..floor();
    final v1 = Vector3(-0.5, 0.5, -0.5)..floor();
    final v2 = Vector3(-0.9, 0.9, -0.5)..floor();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
  }

  void testVector3Ceil() {
    final v0 = Vector3(-0.1, 0.1, -0.1)..ceil();
    final v1 = Vector3(-0.5, 0.5, -0.5)..ceil();
    final v2 = Vector3(-0.9, 0.9, -0.9)..ceil();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
  }

  void testVector3Round() {
    final v0 = Vector3(-0.1, 0.1, -0.1)..round();
    final v1 = Vector3(-0.5, 0.5, -0.5)..round();
    final v2 = Vector3(-0.9, 0.9, -0.9)..round();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
  }

  void testVector3RoundToZero() {
    final v0 = Vector3(-0.1, 0.1, -0.1)..roundToZero();
    final v1 = Vector3(-0.5, 0.5, -0.5)..roundToZero();
    final v2 = Vector3(-0.9, 0.9, -0.9)..roundToZero();
    final v3 = Vector3(-1.1, 1.1, -1.1)..roundToZero();
    final v4 = Vector3(-1.5, 1.5, -1.5)..roundToZero();
    final v5 = Vector3(-1.9, 1.9, -1.9)..roundToZero();

    expect(v0.storage, '');
    expect(v1.storage, '');
    expect(v2.storage, '');
    expect(v3.storage, '');
    expect(v4.storage, '');
    expect(v5.storage, '');
  }

  void testVector3ApplyQuaternion() {
    final q = Quaternion(0.0, 0.9238795292366128, 0.0, 0.38268342717215614);
    final v = Vector3(0.417267069084370, 0.049654430325742, 0.753423475845592)
      ..applyQuaternion(q);

    relativeTest(v,
        Vector3(0.23769846558570862, 0.04965442791581154, -0.8278031349182129));
  }

}